#include "StreamedSound.h"
#include "SoundManager.h"
#include "Sound.h"

#include <string>
#include <iostream>

namespace UniverseSound
{
	StreamedSound::StreamedSound(const Ogre::String &name) : SoundClip(name), mVorbisInfo(0), mVorbisComment(0), mStreamEOF(false)
	{
		mStream = true;
		for (int i = 0; i < NUM_BUFFERS; i++)
			mBuffers[i] = 0;
	}

	StreamedSound::~StreamedSound()
	{
		_release();
		mVorbisInfo = 0;
		mVorbisComment = 0;
		for (int i = 0; i < NUM_BUFFERS; i++)
			mBuffers[i] = 0;
	}

	void StreamedSound::open(Ogre::DataStreamPtr &fileStream)
	{
		int result;

		// Store the file stream
		mAudioStream = fileStream;

		if ((result = ov_open_callbacks(&mAudioStream, &mOggStream, NULL, 0, mOggCallbacks)) < 0)
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StreamedSound::open() - Unable to open audio stream!");
			return;
		}

		// Is file seekable?
		if (ov_seekable(&mOggStream) == 0)
			mSeekable = false;

		// Get Vorbis Info
		mVorbisInfo = ov_info(&mOggStream, -1);
		// Get Vorbis Comment
		mVorbisComment = ov_comment(&mOggStream, -1);

		// Get play time
		mPlayTime = ov_time_total(&mOggStream, -1);

		// Generate audio buffers
		alGenBuffers(NUM_BUFFERS, mBuffers);

		// Check format support
		if (!_queryBufferInfo())
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StreamedSound::open() - The format is not supported!");

		// Set XRAM buffers if XRAM is available
		if (SoundManager::getSingleton().isXRAMSupported())
			SoundManager::getSingleton().setXRAMBuffer(NUM_BUFFERS, mBuffers);

		// File is ready
		mFileOpened = true;
	}

	void StreamedSound::_release()
	{
		ALuint source = AL_NONE;
		setSource(source);
		alDeleteBuffers(NUM_BUFFERS, mBuffers);
		ov_clear(&mOggStream);
		mPlayPosChanged = false;
		mPlayPos = 0.f;
	}

	bool StreamedSound::_queryBufferInfo()
	{
		if (!mVorbisInfo)
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound::StaticSound::_queryBufferInfo() - No vorbis information available!", Ogre::LML_NORMAL);
			return false;
		}

		switch (mVorbisInfo->channels)
		{
		case 1:
			{
				mFormat = AL_FORMAT_MONO16;
				mBufferSize = mVorbisInfo->rate >> 1;
				mBufferSize -= (mBufferSize % 2);
			}
			break;
		case 2:
			{
				mFormat = AL_FORMAT_STEREO16;
				mBufferSize = mVorbisInfo->rate;
				mBufferSize -= (mBufferSize % 4);
			}
			break;
		case 4:
			{
				mFormat = alGetEnumValue("AL_FORMAT_QUAD16");
				if (!mFormat)
					return false;
				mBufferSize = mVorbisInfo->rate * 2;
				mBufferSize -= (mBufferSize % 8);
			}
			break;
		case 6:
			{
				mFormat = alGetEnumValue("AL_FORMAT_51CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mVorbisInfo->rate * 3;
				mBufferSize -= (mBufferSize % 12);
			}
			break;
		case 7:
			{
				mFormat = alGetEnumValue("AL_FORMAT_61CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mVorbisInfo->rate * 4;
				mBufferSize = (mBufferSize % 16);
			}
			break;
		case 8:
			{
				mFormat = alGetEnumValue("AL_FORMAT_71CHN16");
				if (!mFormat)
					return false;
				mBufferSize = mVorbisInfo->rate * 5;
				mBufferSize = (mBufferSize & 20);
			}
			break;
		default:
			Ogre::LogManager::getSingleton().logMessage("UniverseSound - Unable to determine buffer format", Ogre::LML_NORMAL);
			Ogre::LogManager::getSingleton().logMessage("UniverseSound - Trying to set buffer format to default mono ...", Ogre::LML_NORMAL);
			mFormat = AL_FORMAT_MONO16;
			mBufferSize = mVorbisInfo->rate >> 1;
			mBufferSize -= (mBufferSize % 2);
			break;
		}
		return true;
	}

	void StreamedSound::_prebuffer()
	{
		if (mSource == AL_NONE) 
			return;

		int i = 0;
		while (i < NUM_BUFFERS)
		{
			if (_stream(mBuffers[i]))
				alSourceQueueBuffers(mSource, 1, &mBuffers[i++]);
			else
				break;
		}
	}

	void StreamedSound::setSource(ALuint &source)
	{
		if (source != AL_NONE)
		{
			// Set source
			mSource=source;

			// Fill data buffers
			_prebuffer();

			// Init source
			_initSource();
		}
		else
		{
			// Unqueue buffers
			_dequeue();

			// Set source
			mSource = source;
		}
	}

	void StreamedSound::_updateAudioBuffers()
	{
		// Automatically play if ready.
		if (mPlayDelayed)
			play();

		if (mSource == AL_NONE || !mPlay) return;

		ALenum state;
		alGetSourcei(mSource, AL_SOURCE_STATE, &state);

		if (state == AL_STOPPED)
		{
			if(mStreamEOF)
			{
				stop();
				// Finished callback
				if (mFinishedCB && mFinCBEnabled)
					mFinishedCB->execute(static_cast<SoundClip*>(this));
				return;
			}
			else
			{
				alSourcePlay(mSource);
			}
		}

		int processed;

		alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);

		while(processed--)
		{
			ALuint buffer;

			alSourceUnqueueBuffers(mSource, 1, &buffer);
			if (_stream(buffer)) 
				alSourceQueueBuffers(mSource, 1, &buffer);
		}

		// Handle play position change 
		if (mPlayPosChanged) 
		{
			_updatePlayPosition();
		}
	}
	
	bool StreamedSound::_stream(ALuint buffer)
	{
		std::vector<char> audioData;
		char* data;
		int  bytes = 0;
		int  section = 0;
		int  result = 0;

		// Create buffer
		data = new char[mBufferSize];

		// Read only what was asked for
		while(static_cast<int>(audioData.size()) < mBufferSize)
		{
			// Read up to a buffer's worth of data
			bytes = ov_read(&mOggStream, data, static_cast<int>(mBufferSize), 0, 2, 1, &section);
			// EOF check
			if (bytes == 0)
			{
				// If set to loop wrap to start of stream
				if (mLoop)
				{
					ov_time_seek(&mOggStream, 0);
					if (mLoopCB && mLoopCBEnabled)
						mLoopCB->execute(static_cast<SoundClip*>(this));
				}
				else
				{
					mStreamEOF = true;
					// Don't loop - finish playing the clip
					break;
				}
			}
			// Append to end of buffer
			audioData.insert(audioData.end(), data, data + bytes);
			// Keep track of read data
			result+=bytes;
		}

		// EOF
		if(result == 0)
		{
			delete [] data;
			return false;
		}

		alGetError();
		// Copy buffer data
		alBufferData(buffer, mFormat, &audioData[0], static_cast<ALsizei>(audioData.size()), mVorbisInfo->rate);

		// Cleanup
		delete [] data;

		return true;
	}
	
	void StreamedSound::_dequeue()
	{
		if(mSource == AL_NONE)
			return;

		// Stop source to allow unqueuing
		alSourceStop(mSource);

		int queued=0;

		// Get number of buffers queued on source
		alGetError();
		alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &queued);

		while (queued--)
		{
			ALuint buffer;
			// Remove number of buffers from source
			alSourceUnqueueBuffers(mSource, 1, &buffer);

			// Print errors
			if (alGetError() != AL_NO_ERROR) 
				Ogre::LogManager::getSingleton().logMessage("UniverseSound::StreamedSound::_dequeue() - Unable to dequeue buffers");
		}
	}

	void StreamedSound::_updatePlayPosition()
	{
		if (mSource == AL_NONE) 
			return;

		bool paused = isPlaying();
		bool playing = isPaused();

		// Seek...
		pause();
		ov_time_seek(&mOggStream, mPlayPos);

		// Unqueue all buffers
		_dequeue();

		// Fill buffers
		_prebuffer();

		// Reset state..
		if (playing)
			play();
		else if (paused) 
			pause();

		// Set flag
		mPlayPosChanged = false;
	}

	void StreamedSound::setPlayPosition(Ogre::Real seconds)
	{
		if (!mSeekable || seconds < 0.f) 
			return;

		// Wrap time
		if (seconds > mPlayTime)
		{
			do {seconds -= mPlayTime;} while (seconds > mPlayTime);
		}

		// Set position
		mPlayPos = seconds;
	
		// Set flag
		mPlayPosChanged = true;
	}

	void StreamedSound::pause()
	{
		boost::recursive_mutex::scoped_lock l(mMutex);

		if (!mFileOpened)
		{
			if (!mPauseDelayed)
			{
				mPauseDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_PAUSE);
			}
			return;
		}

		if(mSource == AL_NONE)

		alSourcePause(mSource);

		mPauseDelayed = false;
	}
	/*/////////////////////////////////////////////////////////////////*/
	void StreamedSound::play()
	{
		boost::recursive_mutex::scoped_lock l(mMutex);

		if (!mFileOpened)
		{
			if (!mPlayDelayed)
			{
				mPlayDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_PLAY);
			}
			return;
		}

		if (isPlaying())
			return;

		// Grab a source if not already attached
		if (mSource == AL_NONE)
			if (!SoundManager::getSingleton().requestSoundSource(this))
				return;

		alGetError();
		// Play source
		alSourcePlay(mSource);
		if (alGetError())
		{
			Ogre::LogManager::getSingleton().logMessage("UniverseSound: Unknown error while playing a streamed sound");
			return;
		}
		
		// Set play flag
		mPlay = true;
		mPlayDelayed = false;
	}

	void StreamedSound::stop()
	{
		boost::recursive_mutex::scoped_lock l(mMutex);

		if (!mFileOpened)
		{
			if (!mStopDelayed)
			{
				mStopDelayed = true;
				SoundManager::getSingletonPtr()->queueThreadedSound(this, ONLOAD_STOP);
			}
			return;
		}

		if (mSource != AL_NONE)
		{
			// Remove audio data from source
			_dequeue();

			// Stop playback
			mPlay = false;
			mStopDelayed = false;

			// Reset stream pointer
			ov_time_seek(&mOggStream,0);

			// Reload data
			_prebuffer();

			// Give up source immediately if specfied
			if (mGiveUpSource) 
				SoundManager::getSingleton().releaseSoundSource(this);
		}
	}
}